/*
 * > /*
 >  * INSANE - Interactive Structural Analysis Environment
 >  *
 >  * Copyright (C) 2003-2004
 >  * Universidade Federal de Minas Gerais
 >  * Escola de Engenharia
 >  * Departamento de Engenharia de Estruturas
 >  *
 >  * Author's email :     insane@dees.ufmg.br
 >  * Author's Website :   http://www.dees.ufmg.br/insane
 >  *
 >  * This program is free software; you can redistribute it and/or
 >  * modify it under the terms of the GNU General Public License
 >  * as published by the Free Software Foundation; either version 2
 >  * of the License, or any later version.
 >  *
 >  * This program is distributed in the hope that it will be useful,
 >  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 >  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 >  * GNU General Public License for more details.
 >  *
 >  * You should have received a copy of the GNU General Public License
 >  * along with this program; if not, write to the Free Software
 >  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-
 > 1307, USA.
 *
 */
package br.ufmg.dees.insane.ui.rich.prep.controller;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import br.ufmg.dees.insane.commons.controller.Controller;
import br.ufmg.dees.insane.commons.view.DrawingArea;
import br.ufmg.dees.insane.commons.view.IView;
import br.ufmg.dees.insane.draw.AxisDrawPerspective;
import br.ufmg.dees.insane.draw.Draw;
import br.ufmg.dees.insane.draw.LineDraw;
import br.ufmg.dees.insane.draw.PointDraw;
import br.ufmg.dees.insane.model.geo.halfEdgeDataStructure.Edge;
import br.ufmg.dees.insane.model.geo.halfEdgeDataStructure.Face;
import br.ufmg.dees.insane.model.geo.halfEdgeDataStructure.PlanarSubdivision;
import br.ufmg.dees.insane.model.geo.halfEdgeDataStructure.Vertex;
import br.ufmg.dees.insane.model.prep.GeoPrepModel;
import br.ufmg.dees.insane.model.prep.HalfEdgePrepModel;
import br.ufmg.dees.insane.ui.rich.prep.view.GeometricTransformation;
import br.ufmg.dees.insane.ui.rich.prep.view.PrepView;
import br.ufmg.dees.insane.ui.rich.prep.view.PrepViewState;
import br.ufmg.dees.insane.ui.rich.prep.view.command.OpEulerKEV;
import br.ufmg.dees.insane.ui.rich.prep.view.command.OpEulerKVFS;
import br.ufmg.dees.insane.util.IMatrix;
import br.ufmg.dees.insane.util.IPoint3d;

/**
 * The geometric controller for the half-edge model.
 * @author Ferreira, Reginaldo & Pitangueira, Roque
 * @since february 2007
 */

public class PrepControllerHE implements Controller {
    private IView view;
    private GeoPrepModel model;
    private ArrayList<PointDraw> pointsDrawList;
    private ArrayList<LineDraw> linesDrawList;
    private ArrayList<Object> selections;
    private PrepViewState state;
    private GeometricTransformation geoTransform;
    
//  ****************************************************************************
    
    /**
     * The constructor method xyplot_controller, inicializes IView 
     * @param vs a IView
     * @see XYPlotInterface
     */
    public PrepControllerHE(IView vs) {
        this.view = vs;
        this.pointsDrawList  = new ArrayList<PointDraw>();
        this.linesDrawList = new ArrayList<LineDraw>();
        this.selections  = new ArrayList<Object>();
        this.state = ((PrepViewState)this.view.getViewState());
        geoTransform = (((PrepViewState)this.view.getViewState()).getGeoTransformViewState()).getGeoTransform();
        
    }
   
//  ****************************************************************************
    
    public void draw(Graphics2D g) {
    	
    	Draw drawObj;
    	DrawingArea da = this.view.getDrawingArea();
    	
    	da.setBackground(this.view.getViewState().bgcolor);
    	da.setGridColor(this.view.getViewState().gridColor);
    	

    	// Draw the Axis
    	g.setStroke(new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
   	 	drawObj = new AxisDrawPerspective();
   	 	drawObj.setAngle(0);
   	 	((AxisDrawPerspective)drawObj).setLabel1("X");
   	 	((AxisDrawPerspective)drawObj).setLabel2("Y");
   	 	((AxisDrawPerspective)drawObj).setLabel3("Z");
   	 	drawObj.setLocation(new Point(25,((PrepView)this.view).getDrawingArea().getHeight()-25));
	 	drawObj.draw(g);
	 	
	 	
	 	//Draws the Lines
        g.setColor(Color.black);
        g.setStroke(new BasicStroke(1f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
        ListIterator lns = linesDrawList.listIterator();
        while(lns.hasNext())
        {
            drawObj = (Draw)lns.next();
            drawObj.draw(g);
        }

        
	 	// Draws the Points
        g.setColor(this.state.getPointsViewState().getMarkColor());
        g.setStroke(new BasicStroke(2f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
        ListIterator pts = pointsDrawList.listIterator();
        while(pts.hasNext()) {
            drawObj = (Draw)pts.next();
            drawObj.setScale(5);
            drawObj.draw(g);
        }

    } // end Draw
    
//  ****************************************************************************
    
    /** Adds objects in the half-edge model and in a list of drawings */
    public void add(Object obj)
    {
    	DrawingArea da = this.view.getDrawingArea();
    	Point p, p1, p2;
        
    	// add the vertex
        if (obj instanceof Vertex)
        {
        	Vertex vertice = (Vertex)obj;
            p = da.getViewPoint(vertice.getCoords().x,vertice.getCoords().y);
            model.getDataStructure().addVertexInPSList(vertice);
            pointsDrawList.add(new PointDraw(p, vertice));
        }
        
        // add the edge
		if (obj instanceof Edge)
		{
			Edge edge = (Edge)obj;
	        p1 = da.getViewPoint(edge.getVStart().getCoords().x, edge.getVStart().getCoords().y);
	        p2 = da.getViewPoint(edge.getVEnd().getCoords().x, edge.getVEnd().getCoords().y);
			model.getDataStructure().addEdgeInPSList(edge);
			linesDrawList.add(new LineDraw(p1, p2, edge));
		}
		
		// add the face
		if (obj instanceof Face)
		{
			Face face = (Face)obj;
            model.getDataStructure().addFaceInPSList(face);
		}
		
    }// end Add

//  ****************************************************************************
    
    /** Makes drawing objects from model. */
    public void compose()
    {
      PlanarSubdivision plnSub = model.getDataStructure().getPlanarSubdivision();
      DrawingArea da = this.view.getDrawingArea();
      IMatrix matTransf = geoTransform.getMatrix();
      
      // Vertices
      pointsDrawList = new ArrayList<PointDraw>(); 
      Vertex vert;
      Point p;
      
      ListIterator itr_vert = plnSub.getVertexList().listIterator();
      while (itr_vert.hasNext()) {
           IMatrix matPt = new IMatrix(1,4);
           double x, y;
          
           vert = (Vertex)itr_vert.next();
           matPt.setElement(0,0,vert.getCoords().x);
           matPt.setElement(0,1,vert.getCoords().y);
           matPt.setElement(0,2,vert.getCoords().z);
           matPt.setElement(0,3,1);
           matPt.mul(matTransf);
           
           x = matPt.getElement(0,0);
           y = matPt.getElement(0,1);
           p = da.getViewPoint(x,y);
           
           pointsDrawList.add(new PointDraw(p, vert));  
      }
      
      
      // Edges
      linesDrawList = new ArrayList<LineDraw>();
      Edge edge;
	  Point p1, p2;
	  ListIterator itr_edge = plnSub.getEdgeList().listIterator();
	  while (itr_edge.hasNext())
	  {
			edge = (Edge)itr_edge.next();
            IMatrix matP1 = new IMatrix(1,4);
            IMatrix matP2 = new IMatrix(1,4);
            double x1, y1, x2, y2;
     		    
            matP1.setElement(0,0,edge.getVStart().getCoords().x);
            matP1.setElement(0,1,edge.getVStart().getCoords().y);
            matP1.setElement(0,2,edge.getVStart().getCoords().z);
            matP1.setElement(0,3,1);
            matP1.mul(matTransf);
            
            matP2.setElement(0,0,edge.getVEnd().getCoords().x);
            matP2.setElement(0,1,edge.getVEnd().getCoords().y);
            matP2.setElement(0,2,edge.getVEnd().getCoords().z);
            matP2.setElement(0,3,1);
            matP2.mul(matTransf);
            
            x1 = matP1.getElement(0,0);
            y1 = matP1.getElement(0,1);
            
            x2 = matP2.getElement(0,0);
            y2 = matP2.getElement(0,1);
            
	        p1 = da.getViewPoint(x1, y1);
	        p2 = da.getViewPoint(x2, y2);
			linesDrawList.add(new LineDraw(p1, p2, edge));
	  }
        
    }// end Compose
    
//  ****************************************************************************
    
    public void setModel(GeoPrepModel mdl)
    {
        this.model = mdl;	
    }
    
//  ****************************************************************************
    
    public GeoPrepModel getModel()
    {
        return this.model;	
    }    
    
//  ****************************************************************************  
    
    /** Remove the object */
    public void remove(Object obj)
    {
        HalfEdgePrepModel mdlDataStructure = (HalfEdgePrepModel)model.getDataStructure();

    	if (obj instanceof Vertex)
    	{
    		OpEulerKVFS Op1 = new OpEulerKVFS(mdlDataStructure);
        	Op1.removeVertex((Vertex)obj);
    	}  
    	
    	if (obj instanceof Edge)
    	{
    		OpEulerKEV  Op2 = new OpEulerKEV(mdlDataStructure); 
        	Op2.removeEdge((Edge)obj);
    	}
    	
    	this.compose();
    }
    
//  ****************************************************************************
    
    public void removeAll()
    {
    	pointsDrawList.clear();
    	linesDrawList.clear();
    }    

//  ****************************************************************************
    
    /** Remove the objects that was selected. */
    public List removeSelection()
    {
        ArrayList<Object> sel = new ArrayList<Object>();
        HalfEdgePrepModel mdlDataStructure = (HalfEdgePrepModel)model.getDataStructure();
        OpEulerKVFS Op1 = new OpEulerKVFS(mdlDataStructure);
        OpEulerKEV  Op2 = new OpEulerKEV(mdlDataStructure);
                
        PointDraw pd;
        LineDraw ld;
        int index;
        
        ListIterator itr = pointsDrawList.listIterator();
        while(itr.hasNext())
        {
            pd = (PointDraw)itr.next();
            if (pd.isSelected())
            {
            	Vertex vert = (Vertex)pd.getRepresents();
				index = selections.indexOf(pd.getRepresents());
				selections.remove(index);
				Op1.removeVertex(vert);
                sel.add(vert);
            }
        }     
        
		ListIterator lns = linesDrawList.listIterator();
		while(lns.hasNext())
		{
			ld = (LineDraw)lns.next();
			if (ld.isSelected())
			{
				Edge edge = (Edge)ld.getRepresents();
				index = selections.indexOf(ld.getRepresents());
				selections.remove(index);
				Op2.removeEdge(edge);
				sel.add(edge);
			}
		}        

		this.compose();
		return sel;
    }
    
//  ****************************************************************************
    
    /** Verify if the model already contains one point 2d. */
    public boolean contains(Point2D p2d)
    {
        Point p = view.getDrawingArea().getViewPoint(p2d);
        Draw draw;
		
        ListIterator pts = pointsDrawList.listIterator();
        while(pts.hasNext()) {
            draw = (Draw)pts.next();
            if (draw.contains(p))
            {
                return true;
            }
        }
        
        ListIterator lns = linesDrawList.listIterator();
        while(lns.hasNext()) {
            draw = (Draw)lns.next();
            if (draw.contains(p))
            {
                return true;
            }
        }
        
		return false;
    }

//  ****************************************************************************
    
    /** Verify if the model already contains one edge. */
    public boolean contains(Edge newEdge)
    {
    	Edge edge;
        IPoint3d p1n, p2n, p1e, p2e;
        PlanarSubdivision plnSub = model.getDataStructure().getPlanarSubdivision();
        ListIterator itr = plnSub.getEdgeList().listIterator();
        while(itr.hasNext())
        {
            edge = (Edge)itr.next();
            p1e = edge.getVStart().getCoords();
            p2e = edge.getVEnd().getCoords();            
            
            p1n = newEdge.getVStart().getCoords();
            p2n = newEdge.getVEnd().getCoords();
            
            if ( ((p1n == p1e) & (p2n == p2e)) || ((p1n == p2e) & (p2n == p1e)) )
            {	
            	return true;
            }
        }
        return false;    	
    	
    }    
    
//  ****************************************************************************
    
    /** Verify if the model already contains one point 3d. */
    public boolean contains(IPoint3d p3d)
    { 	
    	IPoint3d p;
        PlanarSubdivision plnSub = model.getDataStructure().getPlanarSubdivision();
        ListIterator itr = plnSub.getVertexList().listIterator();
        while(itr.hasNext())
        {
            p = ((Vertex)itr.next()).getCoords();
            if ((p.x == p3d.x) & (p.y == p3d.y) & (p.z == p3d.z))
            {
            	return true;
            }
        }
        return false;
    }
   
//  ****************************************************************************
    
    public Point pick(Point p) {
        return p;
    }
    
//  ****************************************************************************
    
    /** 
     * The method check if the draw is selected
     * @return true if there is any selected draw in the list.
     * @return false if there is no selected draw in the list.
     */
    public boolean isSelected()
    {
		Draw draw;

		ListIterator pts = pointsDrawList.listIterator();
		while(pts.hasNext()) {
			draw = (Draw)pts.next();
			if (draw.isSelected()) {
				return true;
			}
		}
		
		ListIterator lns = linesDrawList.listIterator();
		while(lns.hasNext()) {
			draw = (Draw)lns.next();
			if (draw.isSelected()) {
				return true;
			}
		}
		
		return false;
    }    
    
//  **************************************************************************** 
    
    public void select(Point p)
    {
    	
        Draw draw;
        int index;

        ListIterator pts = pointsDrawList.listIterator();
        while(pts.hasNext()) 
        {
            draw = (Draw)pts.next();
            if (draw.contains(p))
            { 
                if (draw.isSelected())
                {
                    draw.setSelected(false);
                    index = selections.indexOf(draw.getRepresents());
                    selections.remove(index);
                    return;
                }
                else
                {
                    draw.setSelected(true);
                    selections.add(draw.getRepresents());
                    return;
                }
            }
        }
        

		ListIterator lns = linesDrawList.listIterator();
		while(lns.hasNext())
		{
			draw = (Draw)lns.next();
			if (draw.contains(p))
			{
				if (draw.isSelected())
				{
					draw.setSelected(false);
					index = selections.indexOf(draw.getRepresents());
					selections.remove(index);
					return;
				}
				else
				{
					draw.setSelected(true);
					selections.add(draw.getRepresents());
					return;
				}
			}
		}

        
    }
   
    
//  ****************************************************************************
       
    public void selectAll() {
    	
    	Draw draw;
    	ListIterator pts = pointsDrawList.listIterator();
		while(pts.hasNext())
		{
			draw = (Draw)pts.next();
			draw.setSelected(true);
			selections.add(draw.getRepresents());
		}  
		
		
		ListIterator lns = linesDrawList.listIterator();
		while(lns.hasNext()) {
			draw = (Draw)lns.next();
			draw.setSelected(true);
			selections.add(draw.getRepresents());
		}
		
    }

//  ****************************************************************************
    
    /** Remove the selection marks */
    public void unSelectAll()
    {
    	Draw draw;
    	ListIterator pts = pointsDrawList.listIterator();
		while(pts.hasNext()) {
			draw = (Draw)pts.next();
			draw.setSelected(false);
		} 
		
		ListIterator lns = linesDrawList.listIterator();
		while(lns.hasNext()) {
			draw = (Draw)lns.next();
			draw.setSelected(false);
		}
		
    }

//  ****************************************************************************
    
    public ArrayList getSelection()
    {
    	return selections;
    }
    
//  ****************************************************************************
    
    /** Return the represents of an draw object. */
    public Object getRepresents(Point pView)
    {
		Draw drawObj;
		
		// Points
		ListIterator pts = pointsDrawList.listIterator();
		while(pts.hasNext())
		{
			drawObj = (Draw)pts.next();
			if (drawObj.contains(pView))
			{
				drawObj.setSelected(true);
				return drawObj.getRepresents();
			}
		}
		
		// Lines
		ListIterator lns = this.linesDrawList.listIterator();
		while(lns.hasNext())
		{
			drawObj = (Draw)lns.next();
			if (drawObj.contains(pView))
			{
				drawObj.setSelected(true);
				return drawObj.getRepresents();
			}
		}
		
		return null;

    }
    
//  ****************************************************************************
    
    public GeometricTransformation getCurrentTransform()
    {
    	return this.geoTransform;
    }
    
//  ****************************************************************************
    
    public Object get(Point2D po)
    {
    	return null;
    }
	
//	************************************************************************************** 
	
	/** 
	 * The method find the max and minimum to x and y coordinates of the draw's points
	 * @return limits the draw's limits
	 */
	public Rectangle2D.Double findMinMax()
	{
		PlanarSubdivision plnSub = model.getDataStructure().getPlanarSubdivision();
		List vertices = plnSub.getVertexList();
		IMatrix m = new IMatrix(4,4);
		m = geoTransform.getMatrix();
		IMatrix pt = new IMatrix(1,4);
		
		double xMin = 0.0;
		double yMin = 0.0;
		double xMax = 0.0;
		double yMax = 0.0;
		
		for(int i=0;i<vertices.size();i++){
			pt.setElement(0,0,((IPoint3d)((Vertex)vertices.get(i)).getCoords()).x);
			pt.setElement(0,1,((IPoint3d)((Vertex)vertices.get(i)).getCoords()).y);
			pt.setElement(0,2,((IPoint3d)((Vertex)vertices.get(i)).getCoords()).z);
			pt.setElement(0,3,1);
			pt.mul(m);
			
			if(pt.getElement(0,0)<= xMin)
				xMin = pt.getElement(0,0);
			if(pt.getElement(0,1)<= yMin)
				yMin = pt.getElement(0,1);
			if(pt.getElement(0,0) >= xMax)
				xMax = pt.getElement(0,0);
			if(pt.getElement(0,1)>= yMax)
				yMax = pt.getElement(0,1);
		}
		
		yMin -= 0.5;
		yMax += 0.5;
		xMin -= 0.5;
		xMax += 0.5;
		
		Rectangle2D.Double limits = new Rectangle2D.Double(xMin, yMin, xMax, yMax);
		return limits;
	}    
    
//  ****************************************************************************
	  
	// Samuel
	public void fit2()
	{
		// Calculating bounds
		if(view.getViewState().isDistort())
		{
			Rectangle2D.Double limits = findMinMax();
			limits.x = limits.x - 0.5*limits.width;
			limits.y = limits.y - 0.15*limits.height;
			limits.width =  1.5*limits.width;
			limits.height = 1.15*limits.height;
			((PrepView)this.view).getDrawingArea().setZoom(limits);
		}
		else if(!view.getViewState().isDistort())
		{
			Rectangle2D.Double limits = findMinMax();
			Rectangle2D.Double limAt = view.getViewState().getLimits();
			limits.x = limits.x - 0.5*(limAt.width) ;
			limits.y = limits.y - 0.35*(limits.height) ;
			limits.width =  1.35*limits.width;
			limits.height = 1.35*limits.height;
			((PrepView)this.view).getDrawingArea().setZoom(limits);	
		}
	}   
		
//  ****************************************************************************    
    
    public void fit()
    {
        //Calculating bounds
        if (pointsDrawList.size() >= 2){
            Point leftUp = new Point();
            Point rightDown = new Point();
            PointDraw po;
            
            ListIterator pts = pointsDrawList.listIterator();
            po = (PointDraw)pts.next();
            leftUp.x = po.getLocation().x;
            leftUp.y = po.getLocation().y;
            rightDown.x = po.getLocation().x;
            rightDown.y = po.getLocation().y;
            
            while (pts.hasNext()){
                po = (PointDraw)pts.next();
                
                if (po.getLocation().x < leftUp.x){
                    leftUp.x = po.getLocation().x;
                }
                if (po.getLocation().x > rightDown.x){
                    rightDown.x = po.getLocation().x;
                }
                if (po.getLocation().y < leftUp.y){
                    leftUp.y = po.getLocation().y;
                }
                if (po.getLocation().y > rightDown.y){
                    rightDown.y = po.getLocation().y;
                }
            }
            
            //Adding some border
            DrawingArea da = view.getDrawingArea();
//            Rectangle2D lim = view.getViewState().getLimits();
//            double w = lim.getWidth();
//            double add = (w - (rightDown.x - leftUp.x)) / 2;
            
            leftUp.x = leftUp.x - 15;
            leftUp.y = leftUp.y - 15;
            rightDown.x = rightDown.x + 15;
            rightDown.y = rightDown.y + 15;
            
            Point2D p1 = da.getWorldPoint(leftUp);
            Point2D p2 = da.getWorldPoint(rightDown);
            Rectangle2D r = new Rectangle2D.Double(p1.getX(), p2.getY(), p2.getX(), p1.getY());

            da.setZoom(r);
        }
    }    

//  ****************************************************************************
}// end of this class
